package com.colors.follow.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.colors.commons.constant.ApiConstant;
import com.colors.commons.constant.RedisKeyConstant;
import com.colors.commons.exception.ParameterException;
import com.colors.commons.model.domain.ResultInfo;
import com.colors.commons.model.pojo.Follow;
import com.colors.commons.model.vo.ShortDinerInfo;
import com.colors.commons.model.vo.SignInDinerInfo;
import com.colors.commons.utils.AssertUtil;
import com.colors.commons.utils.ResultInfoUtil;
import com.colors.follow.mapper.FollowMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author colors
 * @date 2021/5/11 17:07
 * 关注/取关业务逻辑层
 */
@Service
public class FollowService {

    @Resource
    private FollowMapper followMapper;
    @Resource
    private RestTemplate restTemplate;
    @Value("${service.name.server-oauth2}")
    private String oauthServerName;
    @Resource
    private RedisTemplate redisTemplate;
    @Value("${service.name.server-diners}")
    private String dinersServerName;
    @Value("${service.name.server-feeds}")
    private String feedsServerName;

    /**
     * 获取粉丝列表
     *
     * @param dinerId
     * @return
     */
    public List<Integer> findFollowers(Integer dinerId) {
        AssertUtil.isNotNull(dinerId, "请输入需要查看的人");
        // 获取 diner 的粉丝列表
        Set<Integer> followers = redisTemplate.opsForSet()
                .members(RedisKeyConstant.followers.getKey() + dinerId);
        AssertUtil.isTrue(followers.size() <= 0, "该用户没有粉丝");
        List<Integer> followerIds = new ArrayList<>(followers);
        return followerIds;
    }

    /**
     * 共同好友
     *
     * @param dinersId    目标食客
     * @param accessToken token
     * @return
     */
    public ResultInfo findCommonsFriends(Integer dinersId, String accessToken, String path) {
        AssertUtil.isTrue(dinersId == null || dinersId < 1, "请选择需要查看的人");

        // 获取当前登录人的信息
        SignInDinerInfo loginDinerInfo = loadSignInDinerInfo(accessToken);

        // 当前登录人的关注信息
        String loginDinerKey = RedisKeyConstant.following.getKey() + loginDinerInfo.getId();
        // 获取查看人的关注信息
        String dinerKey = RedisKeyConstant.following.getKey() + dinersId;
        // 计算交集
        Set<Integer> dinersIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);
        // 没有
        if (dinersIds == null || dinersIds.isEmpty()) {
            return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());
        }
        // 通过 ids 获取相应的食客信息（即共同关注的人）
        ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "/findByIds?access_token={accessToken}&ids={ids}",
                ResultInfo.class, accessToken, StrUtil.join(",", dinersIds));

        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            resultInfo.setPath(path);
            return resultInfo;
        }

        // 处理结果
        List<LinkedHashMap> dinersInfoMap = (ArrayList) resultInfo.getData();

        List<ShortDinerInfo> commonsFriends = dinersInfoMap.stream()
                .map(diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), false)).collect(Collectors.toList());
        return ResultInfoUtil.buildSuccess(path, commonsFriends);

    }

    /**
     * 关注/取关
     *
     * @param followDinerId
     * @param accessToken
     * @param isFollowed
     * @param path
     * @return
     */
    public ResultInfo follow(Integer followDinerId, String accessToken, int isFollowed, String path) {
        // 判断是否选择了要关注的人
        AssertUtil.isTrue(followDinerId == null && followDinerId < 1, "请选择要关注的人");
        // 通过 access_token 获取当前登录的用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // 获取关注者和被关注者之间的信息
        Follow follow = followMapper.selectFollow(dinerInfo.getId(), followDinerId);

        // 如果没有关注信息，且要进行关注 -- 添加关注
        if (follow == null && isFollowed == 1) {
            int count = followMapper.save(dinerInfo.getId(), followDinerId);
            if (count == 1) {
                // 添加到redis中的关注集合
                addToRedisSet(dinerInfo.getId(), followDinerId);
                sendSaveOrRemoveFeed(followDinerId, accessToken, 1);
            }
            return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功！", path, "关注成功！");
        }
        // 如果有关注信息，且是已经取关且要再次进行关注 -- 添加关注
        if (follow != null && follow.getIsValid() == 0 && isFollowed == 1) {
            int count = followMapper.save(dinerInfo.getId(), followDinerId);
            if (count == 1) {
                // 添加到redis中的关注和粉丝集合
                addToRedisSet(dinerInfo.getId(), followDinerId);
                sendSaveOrRemoveFeed(followDinerId, accessToken, 1);
            }
            return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功！", path, "关注成功！");
        }
        // 如果有关注信息，且已经关注且要进行取关 -- 取消关注
        if (follow != null && follow.getIsValid() == 1 && isFollowed == 0) {
            int count = followMapper.update(dinerInfo.getId(), isFollowed);
            if (count == 1) {
                // 移除关注集合
                removeToRedisSet(dinerInfo.getId(), followDinerId);
                sendSaveOrRemoveFeed(followDinerId, accessToken, 0);
            }
            return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "取关成功！", path, "取关成功！");
        }
        return ResultInfoUtil.buildSuccess(path, "操作成功");
    }

    /**
     * 发送请求添加或者移除关注人的Feed列表
     *
     * @param followDinerId 关注好友的ID
     * @param accessToken   当前登录用户token
     * @param type          0=取关 1=关注
     */
    private void sendSaveOrRemoveFeed(Integer followDinerId, String accessToken, int type) {
        String feedsUpdateUrl = feedsServerName + "updateFollowingFeeds/"
                + followDinerId + "?access_token=" + accessToken;
        // 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 构建请求体（请求参数）
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("type", type);
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
        restTemplate.postForEntity(feedsUpdateUrl, entity, ResultInfo.class);
    }


    /**
     * 添加到redis中的关注集合
     *
     * @param dinerId       当前用户id
     * @param followDinerId 被关注者id
     */
    private void addToRedisSet(Integer dinerId, Integer followDinerId) {
        redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followDinerId);
        redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);
    }

    /**
     * // 移除关注集合
     *
     * @param dinerId       当前用户id
     * @param followDinerId 被关注者id
     */
    private void removeToRedisSet(Integer dinerId, Integer followDinerId) {
        redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followDinerId);
        redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);

    }


    /**
     * 获取关注者和被关注者之间的信息
     *
     * @param accessToken token
     * @return
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        // 必须需要登录
        AssertUtil.mustLogin(accessToken);

        String url = oauthServerName + "user/me?access_token={accessToken}";

        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }

        return BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),
                new SignInDinerInfo(), false);

    }
}
